package main

import (
	"fmt"
	"log"
	"net"
	"time"
)

// 这个程序实现了一个简单的UDP打洞中转服务器
// UDP打洞(UDP Hole Punching)是一种NAT穿透技术，允许位于不同NAT设备后的两个客户端直接通信
// 中转服务器的作用是帮助两个客户端交换各自的公网IP地址和端口信息，从而实现直接通信

func main() {
	// 创建UDP监听器，监听所有网络接口(IPv4zero)的9981端口
	// IPv4zero表示0.0.0.0，即监听所有可用的网络接口
	listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 9981})
	if err != nil {
		fmt.Println(err)
		return
	}
	// 打印服务器的本地地址信息
	log.Printf("本地地址: <%s> \n", listener.LocalAddr().String())
	
	// 创建一个切片用于存储连接到服务器的客户端地址信息
	// 预分配容量为2，因为UDP打洞通常需要两个客户端
	peers := make([]net.UDPAddr, 0, 2)
	
	// 创建一个缓冲区用于接收UDP数据包
	data := make([]byte, 1024)
	
	// 无限循环，等待客户端连接
	for {
		// 从UDP连接中读取数据
		// n: 读取的字节数
		// remoteAddr: 发送数据的客户端地址信息(包含IP和端口)
		// data: 存储接收到的数据
		n, remoteAddr, err := listener.ReadFromUDP(data)
		if err != nil {
			fmt.Printf("error during read: %s", err)
		}
		
		// 打印接收到的客户端地址和消息内容
		log.Printf("<%s> %s\n", remoteAddr.String(), data[:n])
		
		// 将客户端地址添加到peers切片中
		peers = append(peers, *remoteAddr)
		
		// 当收集到两个客户端的地址信息后，开始进行UDP打洞过程
		if len(peers) == 2 {

			// 打印即将建立的P2P连接信息
			log.Printf("进行UDP打洞,建立 %s <--> %s 的连接\n", peers[0].String(), peers[1].String())
			
			// UDP打洞的核心步骤：交换客户端的公网地址信息
			// 向第一个客户端发送第二个客户端的地址信息
			listener.WriteToUDP([]byte(peers[1].String()), &peers[0])
			// 向第二个客户端发送第一个客户端的地址信息
			listener.WriteToUDP([]byte(peers[0].String()), &peers[1])
			
			// 等待8秒，给客户端足够的时间建立直接连接
			// 在这段时间内，两个客户端会尝试直接连接对方
			time.Sleep(time.Second * 8)
			
			// 中转服务器完成任务后退出
			// 此时两个客户端已经通过UDP打洞建立了直接连接，不再需要中转服务器
			log.Println("中转服务器退出,仍不影响peers间通信")
			return
		}
	}
}
